package polytech.simulation.core;

import java.util.ArrayList;
import polytech.simulation.storage.ProblemDescription;
import polytech.simulation.storage.Tree;

/**
 * Classe de création et remplissage des arbres
 * @author Amirouche
 */
public class TreePopulator {
	
	final public static String B_VALUE = "B";
	final public static String Xstar_VALUE = "X*";
	final public static String D_VALUE = "D";
	
	public static Tree createTree(ProblemDescription data){
		
		// On calcule delta X*
		data.setDeltaX(data.getSigmaX() * Math.sqrt(data.getDeltaT() * 3.0));
		
		// On cherche à determiner J
		data.setJ((int)Math.ceil(0.184 / (data.getKappa() * data.getDeltaT()) ));
		
		// Création de l'arbre
		Tree tree = new Tree();
		
		for(int i = 0; i < data.getT(); i++){
			
			// Pour satisafaire la condition : 
			// Pour tout i, il existe un noeud (i,j) avec |j| < min(i, J)
			int n = Math.min(i, data.getJ());
			for(int j = -n; j <= n; j++){
				
				Tree.Node node = tree.getNode(i, j);
				
				// Indice en j du prochain enfant DOWN relativement à celui courant
				int childj = 0;
				
				// Calcul de x
				double x = data.getKappa() * j * data.getDeltaT();
				
				// Probabilités de branchement
				double pu = 0, pm = 0, pd = 0;
				
				// Cas du branchement de type A
				if(Math.abs(j) < data.getJ()){
					pu = 1./6. + (x * (x - 1.))/2.;
					pm = 2./3. - x * x;
					pd = 1./6. + (x * (x + 1.))/2.;
					childj = -1;
				} else if(j == -data.getJ()){ // Cas du branchement de type B
					pu = 1./6. + (x * (x + 1.))/2.;
					pm = -1./3. - x * (x + 2);
					pd = 7./6. + (x * (x + 3.))/2.;
					childj = 0;
				} else if(j == data.getJ()){ // Cas du branchement de type C
					pu = 7./6. + (x * (x - 3.))/2.;
					pm = -1./3. - x * (x - 2);
					pd = 1./6. + (x * (x - 1.))/2.;
					childj = -2;
				}
				
				// On ajoute le fils DOWN (Si existant, on le rapatrie, sinon, création)
				Tree.Node child = tree.getNode(i+1, j+childj);
				if(child == null){
					child = new Tree.Node(i+1, j+childj);
				}
				node.setdChild(Tree.Position.DOWN, child, pd);
				
				// On ajoute le fils MIDDLE (Si existant, on le rapatrie, sinon, création)
				child = tree.getNode(i+1, (j+childj + 1));
				if(child == null){
					child = new Tree.Node(i+1, (j+childj + 1));
				}
				node.setdChild(Tree.Position.MIDDLE, child, pm);
				
				// On ajoute le fils UP (Si existant, on le rapatrie, sinon, création)
				child = tree.getNode(i+1, (j+childj + 2));
				if(child == null){
					child = new Tree.Node(i+1, (j+childj + 2));
				}
				node.setdChild(Tree.Position.UP, child, pu);
			}
		}
		
		return tree;
	}
	
	public static void populateTree(Tree tree, ProblemDescription data){
		tree.getRoot().setValue(B_VALUE, 1);
		tree.getRoot().setValue(Xstar_VALUE, 0.);
		for(int i = 1; i < data.getT(); i++){
			
			// Pour satisafaire la condition : 
			// Pour tout i, il existe un noeud (i,j) avec |j| < min(i, J)
			int n = Math.min(i, data.getJ());
			for(int j = -n; j <= n; j++){
				Tree.Node node = tree.getNode(i, j);
				double B = 0;
				for(Tree.Node parent : node.getParents()){
					Tree.Position position = parent.getChildPosition(node);
					B += parent.getValue(B_VALUE)*parent.getProbability(position);
				}
				node.setValue(B_VALUE, B);
				node.setValue(Xstar_VALUE, j * data.getDeltaX());
			}
		}
	}
	
	public static void computeA(Tree tree, ProblemDescription data){
		
		ArrayList<Double> a = new ArrayList<Double>();
		
		for(int i = 0; i < data.getT(); i++){
			
			double sum = 0;
			
			// Parcours de tous les j disponibles
			int n = Math.min(i, data.getJ());
			for(int j = -n; j <= n; j++){
				Tree.Node node = tree.getNode(i, j);
				sum += node.getValue(B_VALUE) * Math.exp(node.getValue(Xstar_VALUE));
			}
			
			// calcul du ai
			double ai = Math.log(data.getForwardPrice()[i]/data.getSeasonalityFactor()[i])
						- Math.log(sum);
			
			// Ajouter au tableau retourné
			a.add(i, ai);
		}
		
		data.setA(a);
	}
	
	public static void computeDeseasonlizedSpotPrice(Tree tree, ProblemDescription data){
		for(int i = 0; i < data.getT(); i++){
			
			// Pour satisafaire la condition : 
			// Pour tout i, il existe un noeud (i,j) avec |j| < min(i, J)
			int n = Math.min(i, data.getJ());
			for(int j = -n; j <= n; j++){
				Tree.Node node = tree.getNode(i, j);
				node.setValue(D_VALUE, Math.exp(node.getValue(Xstar_VALUE)+data.getA().get(i)));
			}
		}
	}
}
